#include "Thermostat.h"
#include "Tools.h"
#include "app-common/zap-generated/callback.h"

using namespace app::Clusters::Thermostat::Attributes;
using namespace tools;

/**
 *
 * MinSetpointDeadBand
 * On devices which support the AUTO feature, this attribute shall indicate the minimum difference
 * between the Heat Setpoint and the Cool Setpoint
 *
 * Features
 * bit0: HEAT -> Heating
 * bit1: COOL -> Cooling
 * bit2: OCC -> Occupancy
 * bit5: AUTO -> AutoMode
 */

namespace {
constexpr
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(thermostatAttributes)
    DECLARE_DYNAMIC_ATTRIBUTE(LocalTemperature::Id, TEMPERATURE, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(OutdoorTemperature::Id, TEMPERATURE, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(AbsMinHeatSetpointLimit::Id, TEMPERATURE, 2, 0),  // 700
    DECLARE_DYNAMIC_ATTRIBUTE(AbsMaxHeatSetpointLimit::Id, TEMPERATURE, 2, 0),  // 3000
    DECLARE_DYNAMIC_ATTRIBUTE(AbsMinCoolSetpointLimit::Id, TEMPERATURE, 2, 0),  // 1600
    DECLARE_DYNAMIC_ATTRIBUTE(AbsMaxCoolSetpointLimit::Id, TEMPERATURE, 2, 0),  // 3200
    DECLARE_DYNAMIC_ATTRIBUTE(OccupiedCoolingSetpoint::Id, INT16S, 2, ATTRIBUTE_MASK_WRITABLE),  // 0x0A28 = 2600
    DECLARE_DYNAMIC_ATTRIBUTE(OccupiedHeatingSetpoint::Id, INT16S, 2, ATTRIBUTE_MASK_WRITABLE),  // 0x07D0 = 2000
    DECLARE_DYNAMIC_ATTRIBUTE(MinHeatSetpointLimit::Id, INT16S, 2, 0),  // 700
    DECLARE_DYNAMIC_ATTRIBUTE(MaxHeatSetpointLimit::Id, INT16S, 2, 0),  // 3000
    DECLARE_DYNAMIC_ATTRIBUTE(MinCoolSetpointLimit::Id, INT16S, 2, 0),  // 1600
    DECLARE_DYNAMIC_ATTRIBUTE(MaxCoolSetpointLimit::Id, INT16S, 2, 0),  // 3200
    DECLARE_DYNAMIC_ATTRIBUTE(MinSetpointDeadBand::Id, INT8S, 1, 0),  // 0x19 = 25
    DECLARE_DYNAMIC_ATTRIBUTE(ControlSequenceOfOperation::Id, INT8U, 1, 0),  // 0x04 ControlSequenceOfOperationEnum
    DECLARE_DYNAMIC_ATTRIBUTE(SystemMode::Id, INT8U, 1, ATTRIBUTE_MASK_WRITABLE),  // 0x01 SystemModeEnum
    DECLARE_DYNAMIC_ATTRIBUTE(FeatureMap::Id, BITMAP32, 4, 0),  // 0x23 bitmap32
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

constexpr CommandId thermostatIncomingCommands[] = {
        app::Clusters::Thermostat::Commands::SetpointRaiseLower::Id,
        kInvalidCommandId,
};

EmberAfCluster thermostatCluster = DECLARE_DYNAMIC_CLUSTER(app::Clusters::Thermostat::Id, thermostatAttributes,
                                                                     ZAP_CLUSTER_MASK(SERVER), thermostatIncomingCommands, nullptr);
}

Thermostat::Thermostat(const ::BasicInformation *node)
        : BridgedDevice(node) {
    ChipLogProgress(DeviceLayer, "Thermostat");
    mLocalTemperature = 2500;

    mCoolSetpoint = 2600;
    mHeatSetpoint = 2000;
    mSystemMode = static_cast<uint8_t>(app::Clusters::Thermostat::SystemModeEnum::kCool);
    mClusters.push_back(thermostatCluster);
}

Protocols::InteractionModel::Status
Thermostat::ReadAttribute(ClusterId clusterId, AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) {
    if (clusterId != app::Clusters::Thermostat::Id) {
        return BridgedDevice::ReadAttribute(clusterId, attributeId, buffer, maxReadLength);
    }
    ChipLogProgress(DeviceLayer, "-------- ReadAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength);
    switch (attributeId) {
        case LocalTemperature::Id:
            GetAttr(buffer, maxReadLength, mLocalTemperature);
            break;
        case OutdoorTemperature::Id:
            GetAttr(buffer, maxReadLength, mLocalTemperature);
            break;
        case AbsMinHeatSetpointLimit::Id:
            GetAttr(buffer, maxReadLength, static_cast<int16_t>(700));
            break;
        case AbsMaxHeatSetpointLimit::Id:
            GetAttr(buffer, maxReadLength, static_cast<int16_t>(3000));
            break;
        case AbsMinCoolSetpointLimit::Id:
            GetAttr(buffer, maxReadLength, static_cast<int16_t>(1600));
            break;
        case AbsMaxCoolSetpointLimit::Id:{
            constexpr  int16_t temperature = 3000;
            GetAttr(buffer, maxReadLength, temperature);
            break;
        }
        case OccupiedCoolingSetpoint::Id:
            GetAttr(buffer, maxReadLength, mCoolSetpoint);
            break;
        case OccupiedHeatingSetpoint::Id:
            GetAttr(buffer, maxReadLength, mHeatSetpoint);
            break;
        case MinHeatSetpointLimit::Id:{
            constexpr  int16_t temperature = 700;
            GetAttr(buffer, maxReadLength, temperature);
            break;
        }
        case MaxHeatSetpointLimit::Id:{
            constexpr int16_t temperature = 3000;
            GetAttr(buffer, maxReadLength, temperature);
            break;
        }
        case MinCoolSetpointLimit::Id:{
            constexpr  int16_t temperature = 1600;
            GetAttr(buffer, maxReadLength, temperature);
            break;
        }
        case MaxCoolSetpointLimit::Id:{
            constexpr  int16_t temperature = 3000;
            GetAttr(buffer, maxReadLength, temperature);
            break;
        }
        case MinSetpointDeadBand::Id:{
            constexpr  int8_t temperature = 25;
            GetAttr(buffer, maxReadLength, temperature);
            break;
        }
        case ControlSequenceOfOperation::Id:
            GetAttr(buffer, maxReadLength, app::Clusters::Thermostat::ControlSequenceOfOperationEnum::kCoolingAndHeating);
            break;
        case SystemMode::Id:
            GetAttr(buffer, maxReadLength, mSystemMode);
            break;
        case FeatureMap::Id: {
            uint32_t bitmap = 0x00000000;
            bitmap |= static_cast<uint32_t>(app::Clusters::Thermostat::Feature::kAutoMode);
            bitmap |= static_cast<uint32_t>(app::Clusters::Thermostat::Feature::kCooling);
//            bitmap |= static_cast<uint32_t>(app::Clusters::Thermostat::Feature::kOccupancy);
            bitmap |= static_cast<uint32_t>(app::Clusters::Thermostat::Feature::kHeating);
//            bitmap |= static_cast<uint32_t>(app::Clusters::Thermostat::Feature::kSetback);
//            bitmap |= static_cast<uint32_t>(app::Clusters::Thermostat::Feature::kSetpoints);
//            bitmap |= static_cast<uint32_t>(app::Clusters::Thermostat::Feature::kMatterScheduleConfiguration);
            ChipLogProgress(DeviceLayer, "ReadAttribute FeatureMap: %08x", bitmap);
            GetAttr(buffer, maxReadLength, bitmap);
            break;
        }

        case ClusterRevision::Id:
        {
            int16_t version = 6;
            GetAttr(buffer, maxReadLength, version);
            break;
        }
        default:
            memset(buffer, 0x00, maxReadLength);
            break;
    }
    return Protocols::InteractionModel::Status::Success;
}

ChipError Thermostat::OnReport(const DataBlock *block) {
    if (block->path.clusterId != app::Clusters::Thermostat::Id) {
        return BridgedDevice::OnReport(block);
    }

    if (block->path.attributeId == LocalTemperature::Id) {
        mLocalTemperature = static_cast<uint16_t>(block->data.asInt());
        ChipLogProgress(DeviceLayer, "mLocalTemperature:%d", this->mLocalTemperature);
        NotifyStatusChanged(app::Clusters::Thermostat::Id, LocalTemperature::Id);
    } else if (block->path.attributeId == SystemMode::Id) {
        mSystemMode = static_cast<uint8_t>(block->data.asInt());
        ChipLogProgress(DeviceLayer, "mSystemMode:%d", this->mSystemMode);
        NotifyStatusChanged(app::Clusters::Thermostat::Id, SystemMode::Id);
    } else if (block->path.attributeId == OccupiedCoolingSetpoint::Id) {
        mCoolSetpoint = static_cast<uint16_t>(block->data.asInt());
        ChipLogDetail(DeviceLayer, "mCoolSetPoint:%d", this->mCoolSetpoint);
        NotifyStatusChanged(app::Clusters::Thermostat::Id, OccupiedCoolingSetpoint::Id);
    } else if (block->path.attributeId == OccupiedHeatingSetpoint::Id) {
        mHeatSetpoint = static_cast<uint16_t>(block->data.asInt());
        ChipLogDetail(DeviceLayer, "mHeatSetPoint:%d", this->mHeatSetpoint);
        NotifyStatusChanged(app::Clusters::Thermostat::Id, OccupiedHeatingSetpoint::Id);
    }

    return CHIP_NO_ERROR;
}

ChipError Thermostat::OnReadAttributes() {
    GetRemoteAttribute(app::Clusters::Thermostat::Id, LocalTemperature::Id);
    GetRemoteAttribute(app::Clusters::Thermostat::Id, SystemMode::Id);
    GetRemoteAttribute(app::Clusters::Thermostat::Id, OccupiedCoolingSetpoint::Id);
    GetRemoteAttribute(app::Clusters::Thermostat::Id, OccupiedHeatingSetpoint::Id);
    return CHIP_NO_ERROR;
}


Protocols::InteractionModel::Status
Thermostat::WriteAttribute(ClusterId clusterId, const EmberAfAttributeMetadata *attributeMetadata, uint8_t *buffer) {
    if (clusterId != app::Clusters::Thermostat::Id) {
        return BridgedDevice::WriteAttribute(clusterId, attributeMetadata, buffer);
    }
    ChipLogProgress(DeviceLayer, "AttributeWrite attribute:%x", attributeMetadata->attributeId);
    if (attributeMetadata->attributeId == SystemMode::Id) {
        mSystemMode = *buffer;
        ChipLogProgress(DeviceLayer, "mSystemMode:%d", this->mSystemMode);
        NotifyStatusChanged(app::Clusters::Thermostat::Id, SystemMode::Id);

        auto block = DataBlock(Path(app::Clusters::Thermostat::Id, SystemMode::Id), mSystemMode);
        SendMessage(&block);

    } else if (attributeMetadata->attributeId == OccupiedCoolingSetpoint::Id) {
        mCoolSetpoint =  *reinterpret_cast<uint16_t*>(buffer);
        ChipLogProgress(DeviceLayer, "mCoolSetPoint:%d", this->mCoolSetpoint);
        NotifyStatusChanged(app::Clusters::Thermostat::Id, OccupiedCoolingSetpoint::Id);

        auto block = DataBlock(Path(app::Clusters::Thermostat::Id, OccupiedCoolingSetpoint::Id), mCoolSetpoint);
        SendMessage(&block);

    } else if (attributeMetadata->attributeId == OccupiedHeatingSetpoint::Id) {
        mHeatSetpoint =  *reinterpret_cast<uint16_t*>(buffer);
        ChipLogProgress(DeviceLayer, "mHeatSetPoint:%d", this->mHeatSetpoint);
        NotifyStatusChanged(app::Clusters::Thermostat::Id, OccupiedHeatingSetpoint::Id);

        auto block = DataBlock(Path(app::Clusters::Thermostat::Id, OccupiedHeatingSetpoint::Id), mHeatSetpoint);
        SendMessage(&block);
    }

    return Protocols::InteractionModel::Status::Success;
}

ChipError Thermostat::AttachToIndex(uint16_t index) {
    const ChipError ret = BridgedDevice::AttachToIndex(index);
    if (ret == CHIP_NO_ERROR) {
        emberAfThermostatClusterServerInitCallback(GetEndpoint());
    }
    return ret;
}
